<?php
/**
 * LilFloat Engine
 * 
 * @copyright     Copyright 2010, Miha Nahtigal (http://www.nahtigal.com)
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 * 
 * 
 * This LilFloat behavior for CakePHP allows you to auto convert float values from local
 * format to default format suitable for saving into database
 * 
 */
class LilFloatEngine {
/**
 * An array of settings
 */
    var $_settings = array();

/**
 * Instantiates object and its settings
 * 
 * @param array $settings Array of settings
 * @access public
 * @return void
 */
	public function __construct($settings = array()) {
		$settings = (array)$settings;
		$l = localeconv();
		
		$l = array_merge($l, array_intersect_key((array)Configure::read('LilFloat.locale'), $l));
		if (!empty($settings['locale'])) $l = array_merge($l, array_intersect_key($settings['locale'], $l));
		
		if (empty($settings['decimalSeparator'])) {
			$settings['decimalSeparator'] = Configure::read('LilFloat.decimalSeparator');
			if (!$settings['decimalSeparator']) {
				$settings['decimalSeparator'] = $l['decimal_point'];
			}
		}
		if (!isset($settings['thousandsSeparator'])) {
			$settings['thousandsSeparator'] = Configure::read('LilFloat.thousandsSeparator');
			if (is_null($settings['thousandsSeparator']) || $settings['thousandsSeparator'] === false) {
				$settings['thousandsSeparator'] = $l['thousands_sep'];
			}
		}
		
		if (!isset($settings['currencySymbol'])) {
			$settings['currencySymbol'] = Configure::read('LilFloat.currencySymbol');
			if (is_null($settings['currencySymbol']) || $settings['currencySymbol'] === false) {
				$settings['currencySymbol'] = $l['currency_symbol'];
			}
		}
		
		// overall locale settings
		$settings['l'] = $l;
		
		// allowed empty
		$settings['empty'] = false;
		
		$this->_settings = $settings;
	}
/**
 * Instantiate static instance
 * 
 * @param array $settings Array of scope properties
 * @access public
 * @return object
 */
	public static function getInstance($settings = array()) {
		static $instance = array();
		if (!$instance) {
			$instance = new LilFloatEngine($settings);
		}
		return $instance;
	}
/**
 * Prevent object from clonning itself
 * 
 * @access protected
 * @return void
 */
	protected function __clone() { }
/**
 * Sets new decimal separator
 *
 * @param string $decimalSeparator New decimal separator
 * @access public
 * @return void
 */
	function setDecimalSeparator($decimalSeparator = null) {
		if (!empty($decimalSeparator)) {
			$this->_settings['decimalSeparator'] = $decimalSeparator;
		}
	}
/**
 * Sets new thousands separator
 *
 * @param string $thousandsSeparator New thousands separator
 * @access public
 * @return void
 */
	function setThousandsSeparator($thousandsSeparator = null) {
		$this->_settings['thousandsSeparator'] = $thousandsSeparator;
	}
/**
 * Sets new currency symbol
 *
 * @param string $currencySymbol New currency symbol
 * @access public
 * @return void
 */
	function setCurrencySymbol($currencySymbol = null) {
		$this->_settings['currencySymbol'] = $currencySymbol;
	}
/**
 * decimalSeparator method
 *
 * @access public
 * @return string
 */
	function decimalSeparator() {
		return $this->_settings['decimalSeparator'];
	}
/**
 * thousandsSeparator method
 *
 * @access public
 * @return string
 */
	function thousandsSeparator() {
		return $this->_settings['thousandsSeparator'];
	}
/**
 * moneySymbol method
 *
 * @access public
 * @return string
 */
	function moneySymbol() {
		return $this->_settings['currencySymbol'];
	}
/**
 * Converts decimal number with local separators to general representation eg. 56.78
 * 
 * @param string $number Decimal or int number in local format
 * @param array $options Options which can override default settings
 * @access public
 * @return string
 */
	function delocalize($number, $options = array()) {
		if (is_float($number) || is_int($number)) return $number;
		$settings = array_merge($this->_settings, (array)$options);
		return trim(strtr(
			$number,
			array(
				$settings['thousandsSeparator'] => '',
				$settings['decimalSeparator']   => '.',
				$settings['currencySymbol']     => '',
				'('                             => '',
				')'                             => '',
			)
		)) * 1;
	}
/**
 * Converts decimal number to local representation.
 *
 * @param mixed $number Decimal or intnumber
 * @param array $options Options which can override default settings
 * @access public
 * @return string
 */
	function format($number, $options = array()) {
		return $this->_formatNumber($number, $options);
		/*if ($settings['empty'] && (($number === '') || is_null($number))) return '';
		if (!isset($settings['places'])) $settings['places'] = 2;
		if (($number > -0.00001) && ($number < 0.00001)) $number = 0;
		return number_format((double)$number, $settings['places'], $settings['decimalSeparator'], $settings['thousandsSeparator']);*/
	}
/**
 * Converts decimal number to local monetary representation.
 *
 * @param mixed $number Decimal or int number
 * @param array $options Options which can override default settings
 * @access public
 * @return string
 */
 
	function money($number, $options = array()) {
		return $this->_formatNumber($number, array_merge(array('currency' => true), $options));
	}
/**
 * Converts formats numer or money according to locale
 *
 * @param mixed $number Decimal or int number
 * @param array $options Options which can override default settings $places = 2, $currency = true
 * @access public
 * @return string
 */
	function _formatNumber($number, $options = array()) {
		$settings = array_merge($this->_settings, (array)$options);
		if (!isset($settings['places'])) $settings['places'] = 2;
		if (!isset($settings['currency'])) $settings['currency'] = false;
		
		if ($settings['empty'] && (($number === '') || is_null($number))) return '';
		
		if (($number > -0.00001) && ($number < 0.00001)) $number = 0;
		
		$ret = number_format(abs($number), $settings['places'], $settings['decimalSeparator'], $settings['thousandsSeparator']);
		
		if ($settings['currency']) {
			$cur_symb = $settings['currencySymbol'];
		} else {
			$cur_symb = '';
		}
		
		if ($number >= 0) {
			$positive_sign = $settings['l']['positive_sign'];
			if ($number == 0) $positive_sign = '';
			
			switch ($settings['l']['p_sign_posn']) {
				case 3:
					$cur_symb = $positive_sign . $cur_symb;
					break;
				case 4:
					$cur_symb = $cur_symb . $positive_sign;
					break;
			}
			
			if ($settings['l']['p_cs_precedes']) {
				$ret = trim(implode((($settings['l']['p_sep_by_space']) ? ' ' : ''), array($cur_symb, $ret)));
			} else {
				$ret = trim(implode((($settings['l']['p_sep_by_space']) ? ' ' : ''), array($ret, $cur_symb)));
			}
			switch ($settings['l']['p_sign_posn']) {
				case 0:
					$ret = '(' . $ret . ')';
					break;
				case 1:
					$ret = $positive_sign . $ret;
					break;
				case 2:
					$ret = $ret . $positive_sign;
					break;
			}
		} else if ($number < 0)  {
			switch ($settings['l']['n_sign_posn']) {
				case 3:
					$cur_symb = $settings['l']['negative_sign'] . $cur_symb;
					break;
				case 4:
					$cur_symb = $cur_symb . $settings['l']['negative_sign'];
					break;
			}
			
			if ($settings['l']['n_cs_precedes']) {
				$ret = trim(implode((($settings['l']['n_sep_by_space']) ? ' ' : ''), array($cur_symb, $ret)));
			} else {
				$ret = trim(implode((($settings['l']['n_sep_by_space']) ? ' ' : ''), array($ret, $cur_symb)));
			}
			
			switch ($settings['l']['n_sign_posn']) {
				case 0:
					$ret = '(' . $ret . ')';
					break;
				case 1:
					$ret = $settings['l']['negative_sign'] . $ret;
					break;
				case 2:
					$ret = $ret . $settings['l']['negative_sign'];
					break;
			}
		}
		
		return $ret;
	}
/**
 * Returns regex for number check
 *
 * @param array $options Options which can override default settings, 
 *                       with additional optional "places" parameter which determin number of
 *                       digits after decimal separatpr.
 * @return string Regex
 * @access public
 */
	function getCheckNumberRegex($options = array()) {
		$settings = array_merge($this->_settings, (array)$options);
		
		$regex = '^[-+]?(0|([1-9]([0-9]*';
		if (!empty($settings['thousandsSeparator'])) {
			$regex .= '|([0-9]{0,2}(\\' . $settings['thousandsSeparator'] . '[0-9]{3})*)';
		}
		$regex .= ')))';
		
		if (!empty($settings['places'])) {
			if (is_numeric($settings['places'])) $settings['places'] = '1,' . $settings['places'];
			$regex .= '(\\' . $settings['decimalSeparator'] . '{1}[0-9]{' . $settings['places'] . '})?';
		}
		$regex .= '$';
		return $regex;
	}
/**
 * Checks that a value is a valid float.
 * If no decimal point is found a false will be returned. The sign is optional.
 *
 * @param string $number Decimal or int number in local foramt
 * @return boolean Success
 * @access public
 */
	function isValidFloat($number, $options = array()) {
		return is_int($number) || is_float($number) || preg_match("/" . $this->getCheckNumberRegex($options) . "/", $number);
	}
}